#pragma once

#include "../comm/log.hpp"
#include "../comm/util.hpp"

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <assert.h>
#include <fstream>

// 根据题目列表将题目信息加载进内存,以及进行数据交互

namespace ns_model
{
    using namespace std;
    using namespace ns_log;
    using namespace ns_util;

    // 题目结构体
    struct Question
    {
        string number; // 题目编号
        string title;  // 题目标题
        string star;   // 难度：简单 中等 困难
        int cpu_limit; // 时间要求
        int mem_limit; // 空间要求
        string desc;   // 题目表述
        string header; // 代码格式
        string tail;   // 题目测试用例（必须与header拼接形成完整代码，给后端编译运行判题）
    };

    // 题目列表路径信息
    string questions_list = "./questions/questions.list";

    // 题库路径
    string questions_path = "./questions/";

    class Model
    {
    private:
        // 题号 ： 题目结构体 (建立映射关系)
        unordered_map<string, Question> questions;

    public:
        Model()
        {
            assert(LoadQuestionList(questions_list)); // 建立所有题目编号与题目信息的映射关系
        }

        ~Model() {}

        // 加载题目列表信息(建立全部映射)
        bool LoadQuestionList(const string &question_list)
        {
            ifstream in(question_list); // 打开题目目录文件
            if (!in.is_open())          // 若文件打开失败则退出
            {
                LOG(FATAL) << "加载题库失败，请检查是否存在题库文件"
                           << "\n";
                return false;
            }

            // 读取文件信息
            string line;
            while (getline(in, line)) // 按行读取目录中的题目信息
            {
                // 对应切分信息
                vector<string> tokens;
                StringUtil::SplitString(line, &tokens, " "); // 题目列表切分

                if (tokens.size() != 5) // 编号 题目 难度 时间 空间
                {
                    LOG(WARNING) << "加载部分题目失败，请检查题目列表格式"
                                 << "\n";
                    continue; // 若不满足切分数量则跳过
                }

                // 若满足切分次数，则填充一个题目详情对象
                Question q;
                q.number = tokens[0];                  // 编号
                q.title = tokens[1];                   // 题目
                q.star = tokens[2];                    // 难度
                q.cpu_limit = atoi(tokens[3].c_str()); // 时间
                q.mem_limit = atoi(tokens[4].c_str()); // 空间

                string path = questions_path;
                path += q.number; // 将题库路径与题目编号结合，形成指定题号的文件路径
                path += "/";

                FileUtil::ReadFile(path + "desc.txt", &(q.desc), true);     // 读取题目描述
                FileUtil::ReadFile(path + "header.cpp", &(q.header), true); // 读取代码范式
                FileUtil::ReadFile(path + "tail.cpp", &(q.tail), true);     // 获取用户编写的代码

                questions.insert(make_pair(q.number, q)); // 将题号与题目详细信息建立映射
            }
            LOG(INFO) << "加载题库成功!"<< "\n";
            in.close();
            return true;
        }

        // 获取全部题目
        bool GetAllQuestions(vector<Question> *out)
        {
            if (questions.size() == 0) // 没有题目信息
            {
                LOG(ERROR) << "用户获取题库失败"<< "\n";
                return false;
            }
            for (const auto &q : questions) // 将所有题目信息取出
            {
                out->push_back(q.second);
            }
            return true;
        }

        // 获取一个题目信息
        bool GetOneQuestion(const string &number, Question *q)
        {
            const auto &iter = questions.find(number);
            if (iter == questions.end()) // 没有找到对应题目编号
            {
                LOG(ERROR) << "用户获取题目失败,题目编号：" << number << "\n";
                return false;
            }
            (*q) = iter->second; // 获取单个题目信息
            return true;
        }
        
    };
}
